package com.br.semperparata.components.gui;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;

public class PickList<T extends Comparable<T>> extends JPanel{
	private static final long serialVersionUID = 1L;

	public static final int SINGLE_SELECTION = ListSelectionModel.SINGLE_SELECTION;
	public static final int SINGLE_INTERVAL_SELECTION = ListSelectionModel.SINGLE_INTERVAL_SELECTION;
	public static  final int MULTIPLE_INTERVAL_SELECTION = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION;
	
	private T DEFATUL_PROTOTYPE_CELL_VALUE = null;
	
	private Class<T> classT;
	private List<T> originalContent;
	
	private JList<T> listFrom;
	private JList<T> listTo;

	private JButton btnAddItem;
	private JButton btnAddSelection;
	private JButton btnRemoveSelection;

	private JTextField tfAddItem;
	
	private JLabel label;

	public PickList(List<T> content, boolean sorted, Class<T> classT) {
		this(null, content, sorted, classT);
	}
	
	public PickList(List<T> content, boolean sorted, int selectionMode, Class<T> classT) {
		this(null, content, sorted, selectionMode, classT);
	}
	
	public PickList(String label, List<T> content, boolean sorted, Class<T> classT) {
		this(label, content, sorted, SINGLE_INTERVAL_SELECTION, classT);
	}
	
	public PickList(String label, List<T> content, boolean sorted, int selectionMode, Class<T> classT) {
		this(label, content, sorted, selectionMode, 0, classT);
	}
	
	public PickList(List<T> content, boolean sorted, int selectionMode, int itemRowCount, Class<T> classT) {
		this(null, content, sorted, selectionMode, itemRowCount, classT);
	}
	
	public PickList(String label, List<T> content, boolean sorted, int selectionMode, int itemRowCount, Class<T> classT) {
		super(new BorderLayout(5, 5));
		this.classT = classT;
		try {
			DEFATUL_PROTOTYPE_CELL_VALUE = classT.newInstance();
		} catch (InstantiationException | IllegalAccessException e) {
			e.printStackTrace();
		}
		
		JPanel mainPanel = new JPanel();
		mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
		
		originalContent = content;
		
		T prototypeCellValue = DEFATUL_PROTOTYPE_CELL_VALUE;
		if (prototypeCellValue.toString() == null && content != null && content.size() > 0)
			prototypeCellValue = content.get(0);
			
		if (content != null) {
			for (T p : content) {
				if (p.toString().length() > prototypeCellValue.toString().length())
					prototypeCellValue = p;
			}
		}
		
		listFrom = initList(content, sorted, itemRowCount, prototypeCellValue, selectionMode);
		JScrollPane fromPanel = new JScrollPane(listFrom);
		fromPanel.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
		fromPanel.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		fromPanel.setViewportView(listFrom);

		listTo = initList(null, sorted, listFrom.getVisibleRowCount(), prototypeCellValue, selectionMode);
		JScrollPane toPanel = new JScrollPane(listTo);
		toPanel.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
		toPanel.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		toPanel.setViewportView(listTo);
		
		JPanel listsPanel = createListsPanel(fromPanel, toPanel);
		mainPanel.add(listsPanel);
		
		if (showAddTextfield()) {
			JPanel addItemPanel = createAddItemPanel();
			if (label != null) {
				this.label = new JLabel(label);
				this.add(this.label, BorderLayout.PAGE_START);
			}
			mainPanel.add(addItemPanel);
		}
		
		this.add(mainPanel, BorderLayout.CENTER);		
	}
	
	private JList<T> initList (List<T> content, boolean sorted, int itemRowCount, T prototypeCellValue, int selectionMode) {
		DefaultListModel<T> model = new DefaultListModel<T>();
		
		if (content != null && sorted) {
			Collections.sort(content);
			for (T s : content) {
				model.addElement(s);
			}
		}
		JList<T> list = new JList<T>(model);
		list.setSelectionMode(selectionMode);
		list.setSelectedIndex(-1);
		list.setLayoutOrientation(JList.VERTICAL);
		int visibleRows = model.getSize();
		if ((itemRowCount > model.getSize() && itemRowCount > 0) || model.getSize() == 0)
			visibleRows = itemRowCount;
		
		list.setVisibleRowCount(visibleRows);
		
		if (prototypeCellValue == null)
			prototypeCellValue = DEFATUL_PROTOTYPE_CELL_VALUE;
		list.setPrototypeCellValue(prototypeCellValue);
		
		return list;
	}
	
	private JPanel createButtonPanel() {
		Icon addItemIcon = new ImageIcon("resources/icons/ic_action_new.png");
		Icon addSelectionIcon = new ImageIcon("resources/icons/ic_action_forward.png");
		Icon removeSelectionIcon = new ImageIcon("resources/icons/ic_action_discard.png");
		
		btnAddItem = new JButton(addItemIcon);
		btnAddItem.addActionListener(new AddItemListener());
		btnAddSelection = new JButton(addSelectionIcon);
		btnAddSelection.addActionListener(new AddSelectionListener());
		btnRemoveSelection = new JButton(removeSelectionIcon);
		btnRemoveSelection.addActionListener(new RemoveSelectionListener());
		btnRemoveSelection.setEnabled(false);
		
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
		panel.setAlignmentX(CENTER_ALIGNMENT);
		panel.setAlignmentY(CENTER_ALIGNMENT);
		panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
		
		panel.add(btnAddSelection);
		panel.add(Box.createVerticalStrut(10));
		panel.add(btnRemoveSelection);
		
		return panel;
	}
	
	private JPanel createAddItemPanel() {
		tfAddItem = new JTextField(20);
		
		tfAddItem.addKeyListener(new KeyListener() {
			
			@Override
			public void keyTyped(KeyEvent e) {}
			
			@Override
			public void keyReleased(KeyEvent e) {
				if (e.getKeyCode() == KeyEvent.VK_ENTER) {
					addItem(tfAddItem.getText());
				}
			}
			
			@Override
			public void keyPressed(KeyEvent e) {}
		});
		
		JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 5));

		panel.add(tfAddItem);
		panel.add(Box.createVerticalStrut(5));
		panel.add(btnAddItem);
		
		return panel;
	}
	
	private JPanel createListsPanel(JScrollPane from, JScrollPane to) {
		JPanel panel = new JPanel(new GridBagLayout());
		
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.fill = GridBagConstraints.HORIZONTAL;
		gbc.gridx = 0;
		gbc.gridy = 0;
		gbc.weightx = 1;
		panel.add(from, gbc);
		
		gbc.gridx = 1;
		gbc.weightx = 0.01;
		panel.add(createButtonPanel(), gbc);
		
		gbc.gridx = 2;
		gbc.weightx = 1;
		panel.add(to, gbc);
		
		return panel;
	}
	
	private void addItemToSortedListModel(T item, ListModel<T> model) {
		if (!(model instanceof DefaultListModel))
			return;
		boolean inserted = false;
		for (int index = 0; index < model.getSize() && !inserted; index++) {
			int compare = item.compareTo(model.getElementAt(index));
			if (compare < 0) {
				((DefaultListModel<T>)model).add(index, item);
				inserted = true;
			}
		}
		
		if (!inserted)
			((DefaultListModel<T>)model).addElement(item);
	}
	
	public void addItemListToFromList(List<T> items) {
		for (T item : items) {
			addItemToSortedListModel(item, listFrom.getModel());
		}
	}
		
	public void clearContent() {
		DefaultListModel<T> model;
		
		model= (DefaultListModel<T>)listTo.getModel();
		model.clear();
		
		model= (DefaultListModel<T>)listFrom.getModel();
		model.clear();
		
		listTo.repaint();
		listFrom.repaint();
	}
	
	private void checkEnabledRemoveBtn() {
		if (listTo.getModel().getSize() > 0 && !btnRemoveSelection.isEnabled())
			btnRemoveSelection.setEnabled(true);
		else if (listTo.getModel().getSize() == 0 && btnRemoveSelection.isEnabled())
			btnRemoveSelection.setEnabled(false);
	}
	

	private void addSelection() {
		int[] index = listFrom.getSelectedIndices();
		
		if (index.length > 0 && listFrom.getModel().getSize() > 0) {
		
			int itemCurrentPos = ((DefaultListModel<T>)listFrom.getModel()).indexOf(listFrom.getModel().getElementAt(index[0]));
			
			ArrayList<T> values = new ArrayList<T>();
			for (int i = 0; i < index.length; i++) {
				values.add(listFrom.getModel().getElementAt(index[i]));
			}
			
			for (T value : values) {
				addItemToSortedListModel(value, listTo.getModel());
			}
			
			for (T value : values) {
				((DefaultListModel<T>)listFrom.getModel()).removeElement(value);
			}
		
			int listSize = listFrom.getModel().getSize();
			itemCurrentPos = listSize > itemCurrentPos ? itemCurrentPos : listSize - 1;
		
			listFrom.setSelectedIndex(itemCurrentPos);
			listFrom.ensureIndexIsVisible(itemCurrentPos);
			
			listTo.setSelectedIndex(0);
			
			checkEnabledRemoveBtn();
		}
		listTo.repaint();
	}
	class AddSelectionListener implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			addSelection();
		}
	}
	
	private void removeSelection() {
		int[] index = listTo.getSelectedIndices();
		
		if (index.length > 0 && listTo.getModel().getSize() > 0) {
		
			int itemCurrentPos = ((DefaultListModel<T>)listTo.getModel()).indexOf(listTo.getModel().getElementAt(index[0]));
			
			ArrayList<T> values = new ArrayList<T>();
			for (int i = 0; i < index.length; i++) {
				values.add(listTo.getModel().getElementAt(index[i]));
			}
			
			for (T value : values) {
				if (originalContent.contains(value) && !((DefaultListModel<T>)listFrom.getModel()).contains(value))
					addItemToSortedListModel(value, listFrom.getModel());
			}
			
			for (T value : values) {
				((DefaultListModel<T>)listTo.getModel()).removeElement(value);
			}
			
			int listSize = listTo.getModel().getSize();
			itemCurrentPos = listSize > itemCurrentPos ? itemCurrentPos : listSize - 1;
			
			listTo.setSelectedIndex(itemCurrentPos);
			listTo.ensureIndexIsVisible(itemCurrentPos);
			
			listFrom.setSelectedIndex(0);
			
			checkEnabledRemoveBtn();
		} else if (listTo.getModel().getSize() == 1) {
			addItemToSortedListModel(listTo.getModel().getElementAt(0), listFrom.getModel());
			((DefaultListModel<T>)listTo.getModel()).removeElementAt(0);
			listTo.setSelectedIndex(index[0]);
			listTo.ensureIndexIsVisible(index[0]);
			checkEnabledRemoveBtn();
		}
	}
	
	class RemoveSelectionListener implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			removeSelection();
		}
	}
	
	private void addItem(String value) {
		if (value.trim().length() > 0){
			T item;
			try {
				item = classT.getConstructor(String.class).newInstance(value.trim());
				addItemToSortedListModel(item, listTo.getModel());
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (SecurityException e) {
				e.printStackTrace();
			}
			
		}
		tfAddItem.setText("");
		tfAddItem.requestFocusInWindow();
		checkEnabledRemoveBtn();
	}
	
	private boolean showAddTextfield() {
		boolean show = false;
		try {
			show = classT.getConstructor(String.class) != null;
		} catch (NoSuchMethodException e) {
		} catch (SecurityException e) {
		}
		return show;
	}
	
	class AddItemListener implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			addItem(tfAddItem.getText());
		}
	}
	
	public void setLabelFont(Font font) {
		if (label != null)
			this.label.setFont(font);
	}
	
	public void setListsFont(Font font) {
		this.listFrom.setFont(font);
		this.listTo.setFont(font);
	}
	
	public void setTextfieldFont(Font font) {
		this.tfAddItem.setFont(font);
	}
	
	public void setFonts(Font font) {
		setLabelFont(font);
		setListsFont(font);
		setTextfieldFont(font);
	}
}